Skip to content

Conversation

@Jean-Christian-Cirstea
Copy link

@Jean-Christian-Cirstea Jean-Christian-Cirstea commented Nov 30, 2025

When no reference file is given, truncate performs two stat() syscalls:

  1. The first one to retrieve the size of the file.
  2. The second one to check if the file if a pipe.

This commit fixes this issue by restructuring the code. The main change resides in the elimination of the initial match performed at the top level to select between different scenarios. Instead, the new implementation:

  1. Check if a reference file has been given. If so, retrieve its size.
  2. Determine the given mode. If no mode has been given, TruncateMode::Extend(0) is implied.
  3. Retrieve the size of the file, while checking if it is a pipe. This is the key point of this PR.
  4. Determine the truncate size based on the reference size and the mode. If no reference file has been provided, use the size of the file to be resized as a reference size.
  5. Finally, perform ftruncate() to resize the file.

/// assert_eq!(mode.to_size(fsize), None);
/// ```
fn to_size(&self, fsize: u64) -> u64 {
fn to_size(&self, fsize: u64) -> Option<u64> {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Previously, this method could panic if rounding to 0. Now, the method returns None if rounding to 0.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would it be possible to add a unit test for this? thanks

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already added. See lines 359:361.

Comment on lines +77 to +78
Self::RoundDown(size) => fsize.checked_rem(*size).map(|remainder| fsize - remainder),
Self::RoundUp(size) => fsize.checked_rem(*size).map(|remainder| fsize + remainder),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As previously stated, calling this method when rounding to 0 resulted in a panic. Following the guideline of avoiding panicking code, use checked_rem() method.

Comment on lines +87 to +89
fn is_absolute(&self) -> bool {
matches!(self, Self::Absolute(_))
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny helper function to determine if self is absolute. Useful when checking if an absolute mode was provided with a reference file.

Comment on lines -176 to -184
#[cfg(unix)]
if let Ok(metadata) = metadata(path) {
if metadata.file_type().is_fifo() {
return Err(USimpleError::new(
1,
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
));
}
}
Copy link
Author

@Jean-Christian-Cirstea Jean-Christian-Cirstea Nov 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This check was moved to line 212.

Comment on lines 209 to 223
let file_size = match metadata(path) {
Ok(metadata) => {
#[cfg(unix)]
if metadata.file_type().is_fifo() {
return Err(USimpleError::new(
1,
translate!("truncate-error-cannot-open-no-device", "filename" => filename.to_string_lossy().quote()),
));
}
metadata.len()
}
Ok(m) => m,
Err(_) => 0,
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retrieve the size of the file, while checking if it is a pipe. One single stat() syscall required.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe add this as a comment in the code?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments.

Comment on lines +263 to +277
// Omitting the mode is equivalent to extending a file by 0 bytes.
let mode = match size_string {
Some(string) => match parse_mode_and_size(string) {
Err(error) => {
return Err(USimpleError::new(
1,
translate!("truncate-error-invalid-number", "error" => error),
));
}
Ok(mode) => mode,
},
None => TruncateMode::Extend(0),
};

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Specifying no mode implies TruncateMode::Extend(0).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same, better in the code as comment :)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a comment. Look carefully at line 263. Est-ce que j'aurais dû l'écrire en français pour qu'il soit plus clair ? :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oups pardon :)

Comment on lines -62 to -68
Self::Reduce(size) => {
if *size > fsize {
0
} else {
fsize - size
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The standard library provides saturating_sub(). No need to implement its logic by ourselves.

@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)

Signed-off-by: Jean-Christian CÎRSTEA <[email protected]>
1. The documentation for parse_mode_and_size() has been updated.
2. Added documentation for TruncateMode::is_absolute().

Signed-off-by: Jean-Christian CÎRSTEA <[email protected]>
@github-actions
Copy link

GNU testsuite comparison:

Skip an intermittent issue tests/tail/overlay-headers (fails in this run but passes in the 'main' branch)
Skipping an intermittent issue tests/misc/tee (passes in this run but fails in the 'main' branch)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants